// Entity

Assault.Entity = {}

// New

function Assault.Entity:New(Class)
	if not (Assault.Entities.List[Class]) then return false end
	
	// Table
	
	local Table = {}
	
	// Meta table
	
	setmetatable(Table, self)
	
	// Index
	
	self.__index = self
	
	// Derive
	
	if (Assault.Entities.List[Class].Derived) then
		local Derived = Assault.Entities.List[Class].Derived
		
		// Derive function
		
		local function Derive(Table, Derived)
			if (Assault.Entities.List[Derived].Derived) then
				Derive(Table, Assault.Entities.List[Derived].Derived)
			end
			
			// Merge
			
			table.Merge(Table, Assault.Entities.List[Derived])
		end
		
		// Derive
		
		Derive(Table, Derived)
	end
	
	// Merge
	
	table.Merge(Table, Assault.Entities.List[Class])
	
	// Entity
	
	Table.Entity            = {}
	Table.Entity.Class      = Class
	Table.Entity.Index      = Assault.Entities.Index
	Table.Entity.Position   = Assault.Vector:New(0, 0)
	Table.Entity.Color      = Color(255, 255, 255, 255)
	Table.Entity.Mass       = ASSAULT_DEFAULT_MASS
	Table.Entity.Material   = ASSAULT_DEFAULT_MATERIAL
	Table.Entity.Rotation   = 0
	Table.Entity.Health     = -1
	Table.Entity.Name       = "Entity"
	Table.Entity.Force      = {Direction = 0, Speed = 0}
	Table.Entity.Size       = {W = 0, H = 0}
	Table.Entity.Variables  = {}
	Table.Entity.Parent     = {Entity = false, Rotation = 0, Difference = 0}
	Table.Entity.Physics    = true
	Table.Entity.Collisions = true
	
	// Increase index
	
	Assault.Entities.Index = Assault.Entities.Index + 1
	
	// Return table
	
	return Table
end

// Set entity position

function Assault.Entity:EntitySetPosition(Vector)
	self.Entity.Position = Vector
end

// Get entity position

function Assault.Entity:EntityGetPosition()
	return self.Entity.Position
end

// Entity get position center

function Assault.Entity:EntityGetPositionCenter()
	local Position = self:EntityGetPosition()
	local Size = self:EntityGetSize()
	
	// X and Y
	
	local X = Position.X + (Size.W / 2)
	local Y = Position.Y + (Size.H / 2)
	
	// Return vector
	
	return Assault.Vector:New(X, Y)
end

// Set entity material

function Assault.Entity:EntitySetMaterial(Material)
	self.Entity.Material = Material
end

// Get entity material

function Assault.Entity:EntityGetMaterial()
	return self.Entity.Material
end

// Set entity color

function Assault.Entity:EntitySetColor(Color)
	self.Entity.Color = Color
end

// Get entity color

function Assault.Entity:EntityGetColor()
	return self.Entity.Color
end

// Get entity index

function Assault.Entity:EntityGetIndex()
	return self.Entity.Index
end

// Set entity size

function Assault.Entity:EntitySetSize(W, H)
	self.Entity.Size = {W = W, H = H}
end

// Get entity size

function Assault.Entity:EntityGetSize()
	return self.Entity.Size
end

// Set entity parent

function Assault.Entity:EntitySetParent(Parent)
	// Entity get distance
	
	local Len = self:EntityGetDistance(Parent)
	
	// Set entity parent
	
	self.Entity.Parent = {Entity = Parent, Rotation = self:EntityGetRotation(), Difference = Len}
end

// Get entity parent

function Assault.Entity:EntityGetParent()
	return self.Entity.Parent
end

// Set entity mass

function Assault.Entity:EntitySetMass(Mass)
	self.Entity.Mass = Mass
end

// Get entity mass

function Assault.Entity:EntityGetMass()
	return self.Entity.Mass
end

// Set entity force

function Assault.Entity:EntitySetForce(Direction, Speed)
	self.Entity.Force = {Direction = Direction, Speed = Speed}
end

// Get entity force

function Assault.Entity:EntityGetForce()
	return self.Entity.Force
end

// Set entity variable

function Assault.Entity:EntitySetVariable(Key, Value)
	self.Entity.Variables[Key] = Value
end

// Get entity variable

function Assault.Entity:EntityGetVariable(Key)
	return self.Entity.Variables[Key]
end

// Set entity rotation

function Assault.Entity:EntitySetRotation(Rotation)
	self.Entity.Rotation = Rotation
end

// Get entity rotation

function Assault.Entity:EntityGetRotation()
	return self.Entity.Rotation
end

// Get entity class

function Assault.Entity:EntityGetClass()
	return self.Entity.Class
end

// Set entity name

function Assault.Entity:EntitySetName(String)
	self.Entity.Name = String
end

// Get entity name

function Assault.Entity:EntityGetName()
	return self.Entity.Name
end

// Set entity physics

function Assault.Entity:EntitySetPhysics(Bool)
	self.Entity.Physics = Bool
end

// Get entity physics

function Assault.Entity:EntityGetPhysics()
	return self.Entity.Physics
end

// Set entity collisions

function Assault.Entity:EntitySetCollisions(Bool)
	self.Entity.Collisions = Bool
end

// Get entity collisions

function Assault.Entity:EntityGetCollisions()
	return self.Entity.Collisions
end

// Set entity health

function Assault.Entity:EntitySetHealth(Amount)
	self.Entity.Health = Amount
end

// Get entity health

function Assault.Entity:EntityGetHealth()
	return self.Entity.Health
end

// Entity get bounding box (Foszor)

function Assault.Entity:EntityGetBoundingBox()
	local Center = self:EntityGetPositionCenter()
	local Size = self:EntityGetSize()
	local Rotation = self:EntityGetRotation()
	
	// Temp
	
	local Temp = {}
	
	// Results
	
	local Results = {}
	
	// Loop
	
	for I = 1, 4 do
		if (I == 1) then
			Temp.X = Center.X - (Size.W * 0.5)
			Temp.Y = Center.Y - (Size.H * 0.5)
		elseif (I == 2) then
			Temp.X = Center.X + (Size.W * 0.5)
			Temp.Y = Center.Y - (Size.H * 0.5)
		elseif (I == 3) then
			Temp.X = Center.X + (Size.W * 0.5)
			Temp.Y = Center.Y + (Size.H * 0.5)
		elseif (I == 4) then
			Temp.X = Center.X - (Size.W * 0.5)
			Temp.Y = Center.Y + (Size.H * 0.5)
		end
		
		// Rotate and add it to the results
		
		Results[I] = Assault.Vector:New(Temp.X, Temp.Y)
		Results[I]:VectorRotateAroundVector(Center, Rotation)
	end
	
	// Return results
	
	return Results
end

// Entity damage health

function Assault.Entity:EntityDamageHealth(Attacker, Inflictor, Amount)
	// Damage
	
	local Damage = Assault.Damage:New()
	
	// Set attacker and inflictor
	
	Damage:DamageSetAttacker(Attacker)
	Damage:DamageSetInflictor(Inflictor)
	
	// Set damage
	
	Damage:DamageSetDamage(Amount)
	
	// On entity take damage
	
	local Check = self:OnEntityTakeDamage(Damage)
	
	// Check if the entity can take damage
	
	if (Check) then
		Assault.Gamemode.Active:GamemodeRunHook("OnEntityTakeDamage", self, Damage)
		
		// Entity get health
		
		local HP = self:EntityGetHealth()
		
		// Check HP is not -1
		
		if (HP != -1) then
			local Amount = Damage:DamageGetDamage()
			
			// Entity set health
			
			self:EntitySetHealth(HP - Amount)
			
			// Check is below or equal to 0
			
			if (self:EntityGetHealth() <= 0) then
				self:EntityDestroy(Damage)
			end
		end
	end
end

// Get entity max length

function Assault.Entity:EntityGetMaxLength()
	local Size = self:EntityGetSize()
	
	// W and H
	
	local W = Size.W ^ 2
	local H = Size.H ^ 2
	
	// Result
	
	local Result = math.sqrt(W + H)
	
	// Return result
	
	return Result
end

// Entity get distance

function Assault.Entity:EntityGetDistance(Entity)
	local A = self:EntityGetPosition():Vector3D()
	local B = Entity:EntityGetPosition():Vector3D()
	
	// Return len
	
	return A:Distance(B)
end

// Get front position

function Assault.Entity:GetFrontPosition(Amount)
	local Rotation = self:EntityGetRotation()
	local Position = self:EntityGetPositionCenter()
	
	// Sin and Cos
	
	local Sin = math.sin(math.rad(Rotation)) * Amount
	local Cos = math.cos(math.rad(Rotation)) * Amount
	
	// X and Y
	
	local X = Position.X - Sin
	local Y = Position.Y - Cos
	
	// Return vector
	
	return Assault.Vector:New(X, Y)
end

// Entity render bounding box (Foszor)

function Assault.Entity:EntityRenderBoundingBox()
	// Results
	
	local Results = self:EntityGetBoundingBox()
	
	// Set draw color
	
	surface.SetDrawColor(0, 0, 255, 255)
	
	// Loop
	
	for I = 1, 4 do
		if (I < 4) then
			surface.DrawLine(Results[I].X, Results[I].Y, Results[I + 1].X, Results[I + 1].Y)
		else
			surface.DrawLine(Results[I].X, Results[I].Y, Results[1].X, Results[1].Y)
		end
	end
end

// On entity paint

function Assault.Entity:OnEntityPaint()
	self:EntityRender()
end

// On entity created

function Assault.Entity:OnEntitySpawn()
end

// On entity remove

function Assault.Entity:OnEntityRemove()
end

// On entity think

function Assault.Entity:OnEntityThink()
end

// On entity can collide

function Assault.Entity:OnEntityCanCollide(Entity)
	return true
end

// On entity break

function Assault.Entity:OnEntityDestroy(Damage)
	return true
end

// On entity used

function Assault.Entity:OnEntityUsed(Entity)
end

// On entity hurt

function Assault.Entity:OnEntityTakeDamage(Damage)
	return true
end

// On player key pressed

function Assault.Entity:OnPlayerKeyPress(Key)
end

// On player key released

function Assault.Entity:OnPlayerKeyRelease(Key)
end

// On handle parent

function Assault.Entity:OnHandleParent()
	local Parent = self:EntityGetParent()
	
	// Check if the entity has a valid parent
	
	if (Parent.Entity) then
		local Rotation = Parent:EntityGetRotation()
		local Position = Parent:EntityGetRotation()
		
		// Sin and Cos
		
		local Sin = math.sin(math.rad(Rotation + Parent.Rotation)) * Parent.Difference
		local Cos = math.cos(math.rad(Rotation + Parent.Rotation)) * Parent.Difference
		
		// X and Y
		
		local X = Position.X - Sin
		local Y = Position.Y - Cos
		
		// Vector
		
		local Vector = Assault.Vector:New(X, Y)
		
		// Set entity position
		
		self:EntitySetRotation(Rotation + Parent.Rotation)
		self:EntitySetPosition(Vector)
	end
end

// On handle physics

function Assault.Entity:OnHandlePhysics()
	local Mass = self:EntityGetMass()
	local Force = self:EntityGetForce()
	local Physics = self:EntityGetPhysics()
	local Position = self:EntityGetPosition()
	local Parent = self:EntityGetParent()
	
	// Check physics
	
	if (!Physics or Parent.Entity) then return false end
	
	// Check speed
	
	if (Force.Speed > 0) then
		for I = 1, Force.Speed do
			local Sin = math.sin(math.rad(Force.Direction)) * I
			local Cos = math.cos(math.rad(Force.Direction)) * I
			
			// X and Y
			
			local X = Position.X - Sin
			local Y = Position.Y - Cos
			
			// Vector and set entity position
			
			local Vector = Assault.Vector:New(X, Y) self:EntitySetPosition(Vector)
			
			// Check for collision
			
			local Collision = self:EntityCheckForCollisions()
			
			// Mass
			
			// Check if the new position will collide with any other entities
			
			if (Collision) then
				// Set position
				
				self:EntitySetPosition(Position)
				
				// Subtract
				
				local Subtract = math.Clamp(Mass / 5000, 0, ASSAULT_PHYSICS_BOUNCE)
				
				// Set backwards force
				
				return self:EntitySetForce(-Force.Direction, Force.Speed * (ASSAULT_PHYSICS_BOUNCE - Subtract))
			else
				local Subtract = math.Clamp(Mass / 5000, 0, ASSAULT_PHYSICS_DRAG)
				
				// Set entity force
				
				return self:EntitySetForce(Force.Direction, Force.Speed * (ASSAULT_PHYSICS_DRAG - Subtract))
			end
		end
	end
end

// On bullet hit

function Assault.Entity:OnBulletHit(Bullet)
	Bullet:EntityEmitSound("physics/metal/metal_solid_impact_bullet1.wav", Assault.Level.Player)
end

// On player mouse pressed

function Assault.Entity:OnPlayerMousePressed(Code)
end

// On player mouse released

function Assault.Entity:OnPlayerMouseReleased(Code)
end

// Entity can collide

function Assault.Entity:EntityCanCollide(Entity)
	local First = self:OnEntityCanCollide(Entity)
	local Second = Entity:OnEntityCanCollide(self)
	
	// Check first and second
	
	if (!First or !Second) then return false end
	
	// First
	
	local First = self:EntityGetCollisions()
	local Second = Entity:EntityGetCollisions()
	
	// Check first and second
	
	if (!First or !Second) then return false end
	
	// Return true
	
	return true
end

// Entity shoot bullets

function Assault.Entity:EntityShootBullets(Damage, Direction, Amount, Spread, Speed, Force, Color)
	local Len = self:EntityGetMaxLength()
	local Position = self:GetFrontPosition(Len)
	
	// Create bullets
	
	for I = 1, Amount do
		local Entity = Assault.Entity:New("entity_bullet")
		
		// Position
		
		Entity:EntitySetPosition(Position)
		Entity:EntitySetSize(2, 16)
		Entity:EntitySetRotation(Direction + math.random(-Spread, Spread))
		Entity:EntitySetColor(Color)
		
		// Variables
		
		Entity:EntitySetVariable("Speed", Speed)
		Entity:EntitySetVariable("Creator", self)
		Entity:EntitySetVariable("Damage", Damage)
		Entity:EntitySetVariable("Force", Force)
		
		// Create
		
		Entity:EntitySpawn()
		
		// Increase spread
		
		Spread = Spread + Spread
	end
end

// Entity collides with entity

function Assault.Entity:EntityCollidesWithEntity(Entity)
	local function CheckLinesCollide(VA, VB, VC, VD)
		local XA = VA.X
		local XB = VB.X
		local XC = VC.X
		local XD = VD.X
		
		// Y
		
		local YA = VA.Y
		local YB = VB.Y
		local YC = VC.Y
		local YD = VD.Y
		
		// Denominator
		
		local Denominator = (YD - YC) * (XB - XA) - (XD - XC) * (YB - YA)
		
		// Numerator A and B
		
		local NA = (XD - XC) * (YA - YC) - (YD - YC) * (XA - XC)
		local NB = (XB - XA) * (YA - YC) - (YB - YA) * (XA - XC)
		
		// Check if the denominator is 0
		
		if (Denominator == 0) then
			if (NA == 0 and NB == 0) then
				// They coincide
				
				return false
			end
			
			// They are parallel
			
			return false
		end
		
		// Unknown A and B
		
		local UA = NA / Denominator
		local UB = NB / Denominator
		
		// Check the positions
		
		if (UA >= 0 and UA <= 1 and UB >= 0 and UB <= 1) then
			local X = XA + UA * (XB - XA)
			local Y = YA + UB * (YB - YA)
			
			// They are colliding
			
			return true, X, Y
		end
		
		// They are not colliding
		
		return false
	end
	
	// Check bounding box collides
	
	local function CheckBoundingBoxCollides(BA, BB)
		local A = {
			{BA[1], BA[2]},
			{BA[2], BA[3]},
			{BA[3], BA[4]},
			{BA[4], BA[1]}
		}
		
		// B
		
		local B = {
			{BB[1], BB[2]},
			{BB[2], BB[3]},
			{BB[3], BB[4]},
			{BB[4], BB[1]}
		}
		
		// Loop
		
		for K, V in pairs(A) do
			for B, J in pairs(B) do
				local Collision, X, Y = CheckLinesCollide(V[1], V[2], J[1], J[2])
				
				// Check if there is a collision
				
				if (Collision) then
					return true, X, Y
				end
			end
		end
		
		// Return false
		
		return false
	end
	
	// Box
	
	local A = self:EntityGetBoundingBox()
	local B = Entity:EntityGetBoundingBox()
	
	// Collision
	
	local Collision, X, Y = CheckBoundingBoxCollides(A, B)
	
	// Return collision
	
	return Collision, X, Y
end

// Entity check for collisions

function Assault.Entity:EntityCheckForCollisions(Ignore)
	Ignore = Ignore or {}
	
	// Loop
	
	for K, V in pairs(Assault.Entities.Spawned) do
		if (V != self) then
			local Check = table.HasValue(Ignore, V)
			
			// Check
			
			if (!Check) then
				local Check = self:EntityCanCollide(V)
				
				// Check
				
				if (Check) then
					local Collision, X, Y = self:EntityCollidesWithEntity(V)
					
					// Check if there is a collision
					
					if (Collision) then
						return V, X, Y
					end
				end
			end
		end
	end
end

// Entity emit sound

function Assault.Entity:EntityEmitSound(Sound, Entity)
	local Position = self:EntityGetPosition()
	
	// Sound
	
	Assault.Game.CreateWorldSound(Position, Sound, Entity)
end

// Entity explode

function Assault.Entity:EntityExplode()
	local Position = self:EntityGetPositionCenter()
	local Size = self:EntityGetSize()
	
	// Explosion
	
	Assault.Entities.EntitiesQuickCreate("effect_explosion", Position, Size.W, Size.H)
	
	// Entity remove
	
	self:EntityRemove()
end

// Entity break

function Assault.Entity:EntityDestroy(Damage)
	local Position = self:EntityGetPositionCenter()
	local Size = self:EntityGetSize()
	
	// On entity destroy
	
	local Check = self:OnEntityDestroy(Damage)
	
	// Check if the entity can be destroyed
	
	if (Check) then
		// Gamemode run hook
		
		Assault.Gamemode.Active:GamemodeRunHook("OnEntityDestroy", self, Damage)
		
		// Explosion
		
		Assault.Entities.EntitiesQuickCreate("effect_break", Position, Size.W, Size.H)
		
		// Entity remove
		
		self:EntityRemove()
	end
end

// Entity is in world

function Assault.Entity:EntityIsInWorld()
	local Position = Assault.Level.Loaded:EntityGetPosition()
	
	// Size
	
	local Size = Assault.Level.Loaded:EntityGetSize()
	
	// Other
	
	local Other = self:EntityGetPositionCenter()
	
	// Check
	
	if (Other.X > Position.X and Other.X < Position.X + Size.W and Other.Y > Position.Y and Other.Y < Position.Y + Size.H) then
		return true
	else
		return false
	end
end

// Entity remove

function Assault.Entity:EntityRemove()
	self:OnEntityRemove()
	
	// Gamemode run hook
	
	Assault.Gamemode.Active:GamemodeRunHook("OnEntityRemove", self)
	
	// Remove the entity
	
	Assault.Entities.Spawned[self.Entity.Index] = nil
end

// Entity render

function Assault.Entity:EntityRender()
	// Material
	
	local Material = self:EntityGetMaterial()
	
	// ID
	
	local ID = surface.GetTextureID(Material)
	
	// Size, color, position and rotation of the entity
	
	local Size = self:EntityGetSize()
	local Color = self:EntityGetColor()
	local Position = self:EntityGetPositionCenter()
	local Rotation = self:EntityGetRotation()
	
	// Set texture and draw color then draw the textured rect rotated
	
	surface.SetTexture(ID)
	surface.SetDrawColor(Color.r, Color.g, Color.b, Color.a)
	surface.DrawTexturedRectRotated(Position.X, Position.Y, Size.W, Size.H, Rotation)
	
	// Check
	
	local Check = Assault.Config.RenderBoundingBox:GetBool()
	
	// Entity render bounding box
	
	if (Check) then
		self:EntityRenderBoundingBox()
	end
	
	// Gamemode run hook
	
	Assault.Gamemode.Active:GamemodeRunHook("OnEntityRender", self)
end

// Entity spawn

function Assault.Entity:EntitySpawn()
	self:OnEntitySpawn()
	
	// Insert it into the spawned entities list
	
	Assault.Entities.Spawned[self.Entity.Index] = self
	
	// Gamemode run hook
	
	Assault.Gamemode.Active:GamemodeRunHook("OnEntitySpawn", self)
end